﻿using System;

namespace DotNetRepository
{
    public class EntityRef<TEntity> : IEntityRef<TEntity> where TEntity : class, IEntity
    {
        private Guid _id;
        private TEntity _unSavedEntity;
        private readonly bool _readOnly;
        private readonly bool _allowNull;

        public EntityRef(EntityRefArgs args)
        {
            _readOnly = args.ReadOnly;
            _allowNull = args.AllowNull;
        }

        public EntityRef(bool readOnly, bool allowNull)
        {
            _readOnly = readOnly;
            _allowNull = allowNull;
        }

        public TEntity Value
        {
            get
            {
                TEntity value;
                if (_unSavedEntity != null)
                {
                    value = _unSavedEntity;

                    // Get rid of strong reference if the entity has now been saved
                    if (_unSavedEntity.Version > 0)
                        _unSavedEntity = null;

                    return value;
                }

                value = Repository<TEntity>.GetByID(_id);

                if (value == null && _id != Guid.Empty)
                    _id = Guid.Empty; // value has been deleted

                return value;
            }
            set
            {
                // If the value hasn't changed, do nothing
                if ((_id == Guid.Empty && value == null) ||
                    (_id == value.ID))
                    return;

                if (_id != Guid.Empty && ReadOnly)
                    throw new ArgumentException("Value is a readonly property!");

                if (value == null && !AllowNull)
                    throw new ArgumentException("Value cannot be null!");

                if (PreviousID == Guid.Empty && _id != Guid.Empty)
                    PreviousID = _id;

                // Hold a direct reference to this entity if it hasn't been saved yet
                if (value.Version == 0)
                    _unSavedEntity = value;

                _id = value.ID;
            }
        }

        IEntity IEntityRef.Value { get{ return Value;} }

        public Guid ID
        {
            get { return _id; }
            set
            {
                // if the value hasn't changed, do nothing
                if (_id == value)
                    return;

                if (_id != Guid.Empty && ReadOnly)
                    throw new ArgumentException("ID is a readonly property!");

                if (value == Guid.Empty && _id != Guid.Empty && !AllowNull)
                    throw new ArgumentException("ID cannot be null!");

                if (PreviousID == Guid.Empty && _id != Guid.Empty)
                    PreviousID = _id;

                _id = value;
            }
        }

        public Guid PreviousID { get; private set; }

        public IEntity PreviousValue
        {
            get
            {
                return PreviousID == Guid.Empty ? null : Repository<TEntity>.GetByID(PreviousID);
            }
        }

        public void ResetPreviousValue()
        {
            PreviousID = Guid.Empty;
        }

        public bool PreviousValueIsCached
        {
            get { return PreviousID == Guid.Empty ? false : Repository<TEntity>.IsCached(_id); }
        }

        public bool IsCached
        {
            get
            {
                return _unSavedEntity != null || Repository<TEntity>.IsCached(_id);
            }
        }

        public bool AllowNull
        {
            get { return _allowNull; }
        }

        public bool ReadOnly
        {
            get { return _readOnly; }
        }
    }

}
